--[[
     Created by IntelliJ IDEA.
     User: 毛凯
     Date: 2017/11/27
     Time: 9:38
     参数：ARGV[1]:关键词多个逗号拼接
     参数：ARGV[2]:typeL逗号分隔 1级分类  1,4,7
     参数：ARGV[3]:typeM逗号分隔空 2级分类 [17,18]
     参数：ARGV[4]:typeS逗号分隔 3及分类 [40100,40101]
     参数：ARGV[5]:vip逗号分隔  0 代表普通模板, 10 代表 vip 模板
     参数：ARGV[6]:kwdIds 关键词标签 id逗号分隔
     参数：ARGV[7]:排序   1.按时间排(默认desc)  2.按热度排  3.order排序 4.先按order排序在按update排序
     参数：ARGV[8]:pageNum 页码 0开始
     参数：ARGV[9]:pageSize 页容量
     返回：返回数组：
            0:code,0表示成功，其它表示没搜索到结果中途退出
            1:result，搜索结果
            2:count，总页数
            3:desc, 测试用调试字段
--]]

-- ########################################################
-- 统一模板的前缀designId
local PREFIX_UNI = "d_"
-- 分类的前缀
local PREFIX_TYPE = "type_id_"
-- 热度排序的key
local KEY_HOT_SORT = "hot_sort"
-- 时间排序的key
local KEY_DATE_SORT = "date_sort"
-- order排序的key
local KEY_ORDER_SORT = "order_sort"
-- keyword的前缀
local PREFIX_KEYWORD = "kwd_"
-- 更新时间排序的key
local KEY_UPD_SORT = "upd_sort"
-- 普通模板的集合key
local NORMAL_TMPL = "vip_0"
-- vip模板的集合key
local VIP_TMPL = "vip_10"
-- 分类的前缀
local PREFIX_LABLE = "lable_id_"
--所有设计模板
local ALL_DESIGN = "all"


-- key的连接符号
local PREFIX_S = "*"
-- 数组字符串的分割符号
local PREFIX_ARRAY_SPLIT = ","
--最大分类长度
local MAX_SORTED_SET_LEN = 100

--用户搜索过的 key, ARGV[1]关键词  ARGV[2]type_id   ARGV[3]set_id  ARGV[4]排序字段
local RESULT_K_T_S_S = "RST_"..ARGV[1].."_"..ARGV[2].."_"..ARGV[3].."_" ..ARGV[4].."_"..ARGV[5].."_"..ARGV[6].."_"..ARGV[7]



-- #######################华丽的分割线#################################
function string.split(str, delimiter)
     if str==nil or str=='' or delimiter==nil then
          return nil
     end

     local result = {}
     for match in (str..delimiter):gmatch("(.-)"..delimiter) do
          table.insert(result, match)
     end
     return result
end
-- ######################华丽的分割线##################################
-- 组成返回值数组，code成功值，result搜索结果，count个是总页数,desc调试使用字段
local retData = function(code,result,count, desc)
     local ret = {}
     table.insert(ret, code)

     if(result==nil)
     then
          result='[]'
     end
     table.insert(ret, result)

     if(count==nil)
     then
          count=0
     end
     table.insert(ret, count)

     if(desc==nil)
     then
          desc=''
     end
     table.insert(ret, desc)
     return ret
end


-- #####################华丽的分割线###################################
-- 动态参数调用redis.call
local dynamicParaCall = function(cmd,key,array)
     -- https://stackoverflow.com/questions/42291690/redis-lua-dynamic-number-of-keys-in-redis-call
     local args = {cmd, key}
     local len = #array
     for i= 1, len do
          local key = array[i]
          table.insert(args, key)
     end
     -- unpack the table and pass to redis.pcall
     redis.call(unpack(args))
end

-- ##################华丽的分割线######################################
-- 动态参数调用redis.call,向数组中增加0
local dynamicParaCallZAddRange = function(key,array,start_i,end_i)
     -- https://stackoverflow.com/questions/42291690/redis-lua-dynamic-number-of-keys-in-redis-call
     local args = {'zadd', key}
     local len = #array
     for i= start_i, end_i do
          local mem = array[i]
          table.insert(args, 0)
          table.insert(args, mem)
     end
     -- unpack the table and pass to redis.pcall
     redis.call(unpack(args))
end

-- ####################华丽的分割线####################################
-- 动态参数调用redis.call,向数组中增加0
local dynamicParaCallZAddEx = function(key,array)
     -- https://stackoverflow.com/questions/42291690/redis-lua-dynamic-number-of-keys-in-redis-call
     local args = {'zadd', key}
     local len = #array
     local count = math.floor((len+MAX_SORTED_SET_LEN-1)/MAX_SORTED_SET_LEN)
     for i= 1, count do
          local start_i =  (i-1)*MAX_SORTED_SET_LEN+1
          local end_i =  i*MAX_SORTED_SET_LEN
          if end_i > len then
               end_i = len;
          end
          dynamicParaCallZAddRange(key,array,start_i, end_i)
     end
end

-- ###################华丽的分割线#####################################
--提前返回如果redis存储的集合中没有该集合,说明查询的结果交集肯定为空这种情况提前返回,降低消耗
--判断存储vip集合的key存不存在
local v_1 = tonumber(redis.call("exists",NORMAL_TMPL)) or 0
local v_2 = tonumber(redis.call("exists",VIP_TMPL)) or 0



-- ###################华丽的分割线#####################################
-- 返回结果的方法
local returnFunction = function(cmd,key,page_num,page_size)
     local R_len = redis.call('ZCARD',key)
     --计算总页数
     local count = math.floor((R_len+page_size-1)/page_size)
     local start_num = page_num*page_size
     local end_num = start_num+page_size-1
     --按参数排序
     local pageIds = redis.call(cmd,key,start_num,end_num)

     --提取当前页数据
     local pageIdsLen = #pageIds
     local result='['
     for i= 1, pageIdsLen do
          local one_key = pageIds[i]
          local one_result = redis.call('hget',one_key, 'search_result')
          if(i>1)
          then
               -- 拼接符号
               result = result..PREFIX_ARRAY_SPLIT
          end
          result = result..one_result
     end
     local result=result..']'
     --返回搜索结果
     return retData(0,result,count,nil)
end

-- ####################华丽的分割线####################################
--获取排序的规则，默认1.时间排序    2.热度排序  3.按order排序
local sort = tonumber(ARGV[7]) or 1
-- 计算总页数ARGV[8]页码，ARGV[9]页容量
local page_num = tonumber(ARGV[8]) or 0
local page_size = tonumber(ARGV[9]) or 0


--######################华丽的分割线##################################
-- 先查找redis用户搜索过的结果中有没有  redis方法ZREVRANGE从大到小取,ZRANGE从小到大取
local res = redis.call("exists",RESULT_K_T_S_S)
if(res == 1)
then
     --如果有重新更新缓存过期时间
     redis.call("expire",RESULT_K_T_S_S,86400*1)
     -- 时间倒序大到小取
     if(sort == 1)
     then
          return returnFunction('ZREVRANGE',RESULT_K_T_S_S,page_num,page_size)
     end
     -- 热度倒序大到小取
     if(sort == 2)
     then
          return returnFunction('ZREVRANGE',RESULT_K_T_S_S,page_num,page_size)
     end
     -- 按order排序完后按更新时间排序小到大取
     if(sort == 0)
     then
          return returnFunction('ZRANGE',RESULT_K_T_S_S,page_num,page_size)
     end
end
-- ######################华丽的分割线##################################
-- A表存的交集结果,B表存的是中间表
local A='A'
local B='B'
-- 删除旧数据
redis.call('del',A)
redis.call('del',B)

-- #######################华丽的分割线#################################
-- 分类ID数组typeL为空？
local no_typeL_flg=0
-- ARGV[2] 是typeL
local typeL = ARGV[2]
local tl_len = #typeL
if(typeL==nil or tl_len < 1)
then
     no_typeL_flg = 1
else
     typeL = string.split(typeL, PREFIX_ARRAY_SPLIT)
     tl_len = #typeL
     if(tl_len==0)
     then
          no_typeL_flg = 1
     end
end
-- ARGV[2]：typeL   如果没有传分类则取所有的typeL的并集
if(no_typeL_flg == 0)
then
     -- 如果ARGV[2]有值，则根据ARGV[2]筛选typeL，求筛选之后的并集A
     local array = {}
     for i = 1, tl_len do
          local key1 = PREFIX_TYPE..typeL[i]
          table.insert(array, key1)
     end
     -- 把table封装到redis
     dynamicParaCall('SUNIONSTORE', A, array)
else
     -- 把table封装到redis
     dynamicParaCall('SUNIONSTORE', A,{ALL_DESIGN})
end

-- 保护代码:A没有数据则退出
local A_len = redis.call('SCARD',A)
if(A_len==0)
then
     -- 内部数据错误：typeL数据错误
     return retData(1,nil,nil,'失败typeL并集空')
end

-- #######################华丽的分割线#################################
-- 分类ID数组typeM为空？
local no_typeM_flg = 0
-- ARGV[3] 是typeM
local typeM = ARGV[3]
local tm_len = #typeM
if(typeM == nil or tm_len < 1)
then
     no_typeM_flg = 1
else
     typeM = string.split(typeM, PREFIX_ARRAY_SPLIT)
     tm_len = #typeM
     if(tm_len == 0)
     then
          no_typeM_flg = 1
     end
end
-- ARGV[3]：typeM   如果没有传分类则取所有的typeM的并集
if(no_typeM_flg == 0)
then
     -- 如果ARGV[3]有值，则根据ARGV[3]筛选typeM，求筛选之后的并集B
     local array = {}
     for i = 1, tm_len do
          local key1 = PREFIX_TYPE..typeM[i]
          table.insert(array, key1)
     end
     -- 把table封装到redis
     redis.call('del',B)
     dynamicParaCall('SUNIONSTORE', B, array)
     -- 再与A求交集
     redis.call('SINTERSTORE', A, A, B)
end

-- 保护代码:A没有数据则退出
local A_len = redis.call('SCARD',A)
if(A_len==0)
then
     -- 内部数据错误：typesM数据错误
     return retData(1,nil,nil,'失败typeM并集空')
end

-- #######################华丽的分割线#################################
-- 分类ID数组typeS为空？
local no_typeS_flg = 0
-- ARGV[4] 是typeS
local typeS = ARGV[4]
local s_len = #typeS
if(typeS == nil or s_len < 1)
then
     no_typeS_flg = 1
else
     typeS = string.split(typeS, PREFIX_ARRAY_SPLIT)
     s_len = #typeS
     if(s_len==0)
     then
          no_typeS_flg = 1
     end
end
-- ARGV[4]：typeS  如果没有传分类则取所有的typeS的并集
if(no_typeS_flg == 0)
then
     -- 如果ARGV[4]有值，则根据ARGV[4]筛选typeS，求筛选之后的并集B
     local array = {}
     for i= 1, s_len do
          local key1 = PREFIX_TYPE..typeS[i]
          table.insert(array, key1)
     end
     -- 把table封装到redis
     redis.call('del',B)
     dynamicParaCall('SUNIONSTORE', B, array)
     -- 再与A求交集
     redis.call('SINTERSTORE', A, A, B)
end

-- 保护代码:A没有数据则退出
local A_len = redis.call('SCARD',A)
if(A_len==0)
then
     -- 内部数据错误：typeS数据错误
     return retData(1,nil,nil,'失败typeS并集空')
end

-- #####################华丽的分割线###################################
-- 关键字数组ARGV[1] keywords为空？
local no_keywd_flg=0
local keywords = ARGV[1]
local keywd_len = #keywords
if(keywords == nil or keywd_len < 1)
then
     no_keywd_flg = 1
else
     -- 如果有keyword 有拆分数组取数组长度
     keywords = string.split(keywords, PREFIX_ARRAY_SPLIT)
     keywd_len = #keywords
     if(keywd_len==0)
     then
          no_keywd_flg=1
     end
end

-- 循环取出所有关键词的并集存到redis临时B表
if(no_keywd_flg == 0)
then
     local array = {}
     for i=1, keywd_len do
          local key1 = PREFIX_KEYWORD..PREFIX_S..string.upper(keywords[i])..PREFIX_S
          local keywd_keys = redis.call('keys',key1)
          local k_len = #keywd_keys
          for i=1, k_len do
               table.insert(array,keywd_keys[i])
          end
     end
     -- 把table封装到redis
     redis.call('del',B)
     dynamicParaCall('SUNIONSTORE', B, array)
     -- 再与A求交集
     redis.call('SINTERSTORE', A, A, B)
end

-- 保护代码:A没有数据则退出
local A_len = redis.call('SCARD',A)
if(A_len==0)
then
     -- 内部数据错误：types数据错误
     return retData(1,nil,nil,'失败关键词并集空')
end

-- ####################华丽的分割线####################################
-- vip 标记是否需要vip模板 0普通  10vip模板
local no_vip_flg = 0
-- ARGV[5] 是vip
local vip = ARGV[5]
local v_len = #vip
if(vip == nil or v_len < 1)
then
     no_vip_flg = 1
else
     vip = string.split(vip, PREFIX_ARRAY_SPLIT)
     v_len = #vip
     if(v_len == 0)
     then
          no_vip_flg = 1
     end
end
-- ARGV[5]：vip  如果没有传分类则取所有的vip的并集
if(no_vip_flg==0)
then
     -- 如果ARGV[5]有值，则根据ARGV[5]筛选vip，求筛选之后的并集B
     local array = {}
     for i= 1, v_len do
          local key1 = 'vip_'..vip[i]
          table.insert(array, key1)
     end
     -- 把table封装到redis
     redis.call('del',B)
     dynamicParaCall('SUNIONSTORE', B, array)
     -- 再与A求交集
     redis.call('SINTERSTORE', A, A, B)
end

-- 保护代码:E没有数据则退出
local A_len = redis.call('SCARD',A)
if(A_len==0)
then
     -- 内部数据错误：vip数据错误
     return retData(1,nil,nil,'失败vip并集空')
end

-- ####################华丽的分割线####################################
-- kwdIds标记模板的关键词id集合
local no_kwdId_flg = 0
-- ARGV[6] 是 kwdIds
local kwdIds = ARGV[6]
local kwd_len = #kwdIds
if(kwdIds == nil or kwd_len < 1)
then
     no_kwdId_flg = 1
else
     kwdIds = string.split(kwdIds, PREFIX_ARRAY_SPLIT)
     kwd_len = #kwdIds
     if(kwd_len == 0)
     then
          no_kwdId_flg = 1
     end
end
-- ARGV[6]：kwdIds   如果没有传分类则取所有的kwdIds的并集
if(no_kwdId_flg == 0)
then
     -- 如果ARGV[6]有值，则根据ARGV[6]筛选kwdIds，求筛选之后的并集B
     local array = {}
     for i= 1, kwd_len do
          local key = PREFIX_LABLE..kwdIds[i]
          table.insert(array, key)
     end
     -- 把table封装到redis
     redis.call('del',B)
     dynamicParaCall('SUNIONSTORE', B, array)
     -- 再与A求交集
     redis.call('SINTERSTORE', A, A, B)
end

-- 保护代码:F没有数据则退出
local A_len = redis.call('SCARD',A)
if(A_len==0)
then
     -- 内部数据错误：kwdIds数据错误
     return retData(1,nil,nil,'失败kwdIds并集空')
end

-- ####################华丽的分割线####################################

local ids = redis.call('SMEMBERS',A)
--清除临时表
redis.call('del',B)
--A表中最后的结果字段ids给B表
dynamicParaCallZAddEx(B, ids)

--默认排序方式倒序
local sort_cmd = 'ZREVRANGE';
-- 按时间排序
if(sort == 1)
then
     redis.call('ZINTERSTORE',RESULT_K_T_S_S,2,B,KEY_DATE_SORT)
end
-- 按热度排序
if(sort == 2)
then
     redis.call('ZINTERSTORE',RESULT_K_T_S_S,2,B,KEY_HOT_SORT)
end
-- 先按order排序在按更新时间排序
if(sort == 0)
then
     redis.call('ZINTERSTORE',RESULT_K_T_S_S,2,B,KEY_UPD_SORT)
     --正序排序
     sort_cmd = 'ZRANGE';
end

--设置用户搜索过的key过期时间
redis.call("expire",RESULT_K_T_S_S,3600*1)
return returnFunction(sort_cmd,RESULT_K_T_S_S,page_num,page_size)
